/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.wsinfra.wsrm.impl;

import java.util.Calendar;

import javax.xml.namespace.QName;

import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;

import cgl.narada.wsinfra.exception.ParsingException;
import cgl.narada.wsinfra.schemas.policy.AppliesToDocument;
import cgl.narada.wsinfra.schemas.policy.PolicyAttachmentDocument;
import cgl.narada.wsinfra.schemas.policy.PolicyDocument;
import cgl.narada.wsinfra.schemas.soap.EnvelopeDocument;
import cgl.narada.wsinfra.schemas.utility.ExpiresDocument;
import cgl.narada.wsinfra.schemas.wsrm.AcknowledgementIntervalDocument;
import cgl.narada.wsinfra.schemas.wsrm.BaseRetransmissionIntervalDocument;
import cgl.narada.wsinfra.schemas.wsrm.ExponentialBackoffDocument;
import cgl.narada.wsinfra.schemas.wsrm.InactivityTimeoutDocument;
import cgl.narada.wsinfra.schemas.wsrm.SequenceRefDocument;
import cgl.narada.wsinfra.util.QNameLocator;
import cgl.narada.wsinfra.util.XmlDateTimeConversion;
import cgl.narada.wsinfra.wsp.WspQNames;
import cgl.narada.wsinfra.wsrm.WsrmQNames;
import cgl.narada.wsinfra.wsrm.WsrmSequencePolicies;
import cgl.narada.wsinfra.wsrm.WsrmSequencePolicyFactory;
/** 
    This is a utility class which facilitates the creation of WsrmSequence
    Policies, and also the addition of policy elements besides the 
    serialization/de-serialization of the policies.
     
    @author Shrideep Pallickara
    $Date: 2005/07/29 22:41:30 $
    $Revision: 1.6 $
*/

public class WsrmSequencePolicyFactoryImpl 
  implements WsrmSequencePolicyFactory {

  private static final WsrmSequencePolicyFactory instance = new WsrmSequencePolicyFactoryImpl();
  private WsrmSequencePolicies defaultsForMissingPolicies;

  private WsrmQNames wsrmQNames;
  private WspQNames wspQNames;
  private QNameLocator qNameLocator;
  private XmlDateTimeConversion xmlDateTimeConversion;

  /** The default number of months a sequence would be valid for */
  private int defaultNumOfMonthsValid;
  private String moduleName = "WsrmSequencePolicyFactory: ";
  
  
  private WsrmSequencePolicyFactoryImpl() {
    wsrmQNames = WsrmQNames.getInstance();
    wspQNames = WspQNames.getInstance();

    qNameLocator = QNameLocator.getInstance();
    xmlDateTimeConversion = XmlDateTimeConversion.getInstance();

    defaultsForMissingPolicies = getDefaultWsrmSequencePolicies();
  }
  

  public static WsrmSequencePolicyFactory getInstance() {
    return instance;
  }
  
  /** Construct a sequence policies instance using the XmlCursor instance
      that has been provided. */
  public WsrmSequencePolicies 
  createWsrmSequencePolicies(XmlObject xmlObject) {
    XmlCursor policiesCursor = xmlObject.newCursor();
    /** Used for Moving the policies cursor to the same position as 
	the moveTo cursor.*/
    XmlCursor moveTo = xmlObject.newCursor();
    WsrmSequencePoliciesImpl wsrmSequencePolicies=null;

    AcknowledgementIntervalDocument acknowledgementIntervalDocument = null;
    BaseRetransmissionIntervalDocument baseRetransmissionIntervalDocument=null;
    InactivityTimeoutDocument inactivityTimeoutDocument = null;
    ExponentialBackoffDocument exponentialBackoffDocument = null;
    Calendar sequenceExpiration = null;

    try {
      acknowledgementIntervalDocument = 
	getAcknowledgementInterval(policiesCursor);
    } catch (ParsingException parsingEx) {
      /** No need to report anything. Simply implies element not there or 
	  there are problems */
    }
    policiesCursor.toCursor(moveTo);

    try {
      baseRetransmissionIntervalDocument = 
	getRetransmissionInterval(policiesCursor);
    } catch (ParsingException parsingEx) {
      /** No need to report anything. Simply implies element not there or 
	  there are problems */
    }
    policiesCursor.toCursor(moveTo);
    
    
    try {
      inactivityTimeoutDocument = getInactivityTimeout(policiesCursor);
    } catch (ParsingException parsingEx) {
      /** No need to report anything. Simply implies element not there or 
	  there are problems */
    }
    policiesCursor.toCursor(moveTo);
    
    try {
      exponentialBackoffDocument = getExponentialBackoff(policiesCursor);
    } catch (ParsingException parsingEx) {
      /** No need to report anything. Simply implies element not there or 
	  there are problems */
    }
    
    policiesCursor.toCursor(moveTo);
    try {
      sequenceExpiration = getExpires(policiesCursor);
    } catch (ParsingException parsingEx) {
      /** No need to report anything. Simply implies element not there or 
	  there are problems */
    }

    policiesCursor.dispose();
    moveTo.dispose();
    
    wsrmSequencePolicies = 
      new WsrmSequencePoliciesImpl(sequenceExpiration,
				   inactivityTimeoutDocument,
				   baseRetransmissionIntervalDocument,
				   exponentialBackoffDocument,
				   acknowledgementIntervalDocument);
    
    fillInMissingPolicies(wsrmSequencePolicies);
    return wsrmSequencePolicies;
  }


  /** Get a default wsrm sequence policies element */
  public WsrmSequencePolicies getDefaultWsrmSequencePolicies() {
    String defaultTimingProfile = getDefaultTimingProfile();
    WsrmSequencePolicies wsrmSequencePolicies = null;
        
    try {
      PolicyDocument policyDocument = 
	PolicyDocument.Factory.parse(defaultTimingProfile);  
      wsrmSequencePolicies = createWsrmSequencePolicies(policyDocument);
    } catch (XmlException xmlEx) {
      System.out.println(moduleName + 
			 "Problems parsing default timing-profile");
    }
    return wsrmSequencePolicies;
  }
  

  /** Update the expiry associated with a sequence. This can happen due to
      a new expiry value being specified within a Sequence element. */
  public void updateExpiry(WsrmSequencePolicies wsrmSequencePolicies,
			   Calendar expiresAt) {
    if (wsrmSequencePolicies == null || expiresAt == null) {
      System.out.println(moduleName + "Won't updatesequence expiration since "
			 + "WsrmSequencePolicies=" + wsrmSequencePolicies +
			 ", ExpiresAt=" + expiresAt);
    }
    WsrmSequencePoliciesImpl wsrmSequencePoliciesImpl =
      (WsrmSequencePoliciesImpl) wsrmSequencePolicies;
    wsrmSequencePoliciesImpl.resetSequenceExpiration( expiresAt);
  }
 


  
   /** Retrieves the acknowledgement interval from within the specified 
      XmlCursor */
  public AcknowledgementIntervalDocument 
  getAcknowledgementInterval(XmlCursor xmlCursor) throws ParsingException {
    String element = "[AcknowledgementInterval] element.";
    QName qName = wsrmQNames.getAcknowledgementInterval();
    
    boolean found = qNameLocator.locateQName(xmlCursor, qName);
    
    if (!found) {
      throwParsingException("Unable to locate " + element);
    }

    AcknowledgementIntervalDocument acknowledgementIntervalDocument = null;
    
    try {
      acknowledgementIntervalDocument =
	AcknowledgementIntervalDocument.Factory.parse( xmlCursor.xmlText() );
    } catch (XmlException xmlEx) {
      throwParsingException("Problems parsing " + element);
    }
    
    return acknowledgementIntervalDocument;
  }
  

  /** Retrieves the retransmission interval from within the specified
      XmlCursor */
  public BaseRetransmissionIntervalDocument 
  getRetransmissionInterval(XmlCursor xmlCursor) throws ParsingException {
    String element = "[BaseRetransmissionInterval] element.";
    QName qName = wsrmQNames.getBaseRetransmissionInterval();
    
    boolean found = qNameLocator.locateQName(xmlCursor, qName);
    
    if (!found) {
      throwParsingException("Unable to locate " + element);
    }

    BaseRetransmissionIntervalDocument baseRetransmissionIntervalDocument=null;
    
    try {
      baseRetransmissionIntervalDocument =
	BaseRetransmissionIntervalDocument.Factory.parse( xmlCursor.xmlText());
    } catch (XmlException xmlEx) {
      throwParsingException("Problems parsing " + element);
    }
    
    return baseRetransmissionIntervalDocument;
  }


  /** Retrieves the Inactivity timeout from within the specified
      XmlCursor */
  public InactivityTimeoutDocument 
  getInactivityTimeout(XmlCursor xmlCursor) throws ParsingException {
    String element = "[InactivityTimeout] element.";
    QName qName = wsrmQNames.getInactivityTimeout();
    
    boolean found = qNameLocator.locateQName(xmlCursor, qName);
    
    if (!found) {
      throwParsingException("Unable to locate " + element);
    }

    InactivityTimeoutDocument inactivityTimeoutDocument = null;
    
    try {
      inactivityTimeoutDocument =
	InactivityTimeoutDocument.Factory.parse( xmlCursor.xmlText() );
    } catch (XmlException xmlEx) {
      throwParsingException("Problems parsing " + element);
    }
    
    return inactivityTimeoutDocument;
  }


  /** Gets the exponential from within the specified XmlCursor */
  public ExponentialBackoffDocument 
  getExponentialBackoff(XmlCursor xmlCursor) throws ParsingException {
    String element = "[ExponentialBackoff] element.";
    QName qName = wsrmQNames.getExponentialBackoff();
    
    boolean found = qNameLocator.locateQName(xmlCursor, qName);
    
    if (!found) {
      throwParsingException("Unable to locate " + element);
    }

    ExponentialBackoffDocument exponentialBackoffDocument = null;
    
    try {
      exponentialBackoffDocument =
	ExponentialBackoffDocument.Factory.parse( xmlCursor.xmlText() );
    } catch (XmlException xmlEx) {
      throwParsingException("Problems parsing " + element);
    }
    
    return exponentialBackoffDocument;
  }
  

  /** Gets the SequenceRef Document from within the specified XmlCursor */
  public SequenceRefDocument
  getSequenceRef(XmlCursor xmlCursor) throws ParsingException {
    String element = "[SequenceRef] element.";
    QName qName = wsrmQNames.getSequenceRef();
    
    boolean found = qNameLocator.locateQName(xmlCursor, qName);
    
    if (!found) {
      throwParsingException("Unable to locate " + element);
    }

    SequenceRefDocument sequenceRefDocument = null;
    
    try {
      sequenceRefDocument =
	SequenceRefDocument.Factory.parse( xmlCursor.xmlText() );
    } catch (XmlException xmlEx) {
      throwParsingException("Problems parsing " + element);
    }
    
    return sequenceRefDocument;
  }


  public Calendar
  getExpires(XmlCursor xmlCursor) throws ParsingException {
    String element = "[Expires] element.";
    QName qName = wsrmQNames.getExpires();
    
    boolean found = qNameLocator.locateQName(xmlCursor, qName);
    
    if (!found) {
      throwParsingException("Unable to locate " + element);
    }

    ExpiresDocument expiresDocument = null;
    Calendar expiresAt = null;

    try {
      expiresDocument =
	ExpiresDocument.Factory.parse( xmlCursor.xmlText() );
      expiresAt =  xmlDateTimeConversion.getExpires( expiresDocument );
    } catch (XmlException xmlEx) {
      throwParsingException("Problems parsing " + element);
    }
    
    return expiresAt;
  }


  private void throwParsingException(String reason) 
    throws ParsingException {
    throw new ParsingException(moduleName + reason);
  }



  private void fillInMissingPolicies(WsrmSequencePoliciesImpl parsedPolicies) {
    /** The first time defaultsForMissingPolicies is being created, it will
	also call this method. DO this check to account for that scenario. */
    if (defaultsForMissingPolicies == null) {
      System.out.println(moduleName + "Defaults for missing policies is NULL");
      return;
    }

    if (!parsedPolicies.hasSequenceExpiration() ) {
      Calendar sequenceExpiry = Calendar.getInstance();
      sequenceExpiry.add(Calendar.MONTH, defaultNumOfMonthsValid);
      parsedPolicies.resetSequenceExpiration(sequenceExpiry);
    }

    if (!parsedPolicies.hasAcknowledgementInterval()) {
      parsedPolicies.setAcknowledgementInterval(defaultsForMissingPolicies.getAcknowledgementInterval() );
    }
    
    if (!parsedPolicies.hasInactivityTimeout()) {
      parsedPolicies.setInactivityTimeout(defaultsForMissingPolicies.getInactivityTimeout() );
    }

    if (!parsedPolicies.hasRetransmissionInterval() ) {
      parsedPolicies.setRetransmissionInterval(defaultsForMissingPolicies.getRetransmissionInterval() );
      
    }
    
    if (!parsedPolicies.hasExponentialBackoff()) {
      parsedPolicies.setExponentialBackoff(defaultsForMissingPolicies.getExponentialBackoff());
    }
    
  }


  /** The efficiency of WS-ReliableMessaging implementations can be improved 
      using widely known timing profiles. The following profile can be found 
      at: http://schemas.xmlsoap.org/ws/2004/03/rm/baseTimingProfile.xml */
  private String getDefaultTimingProfile() {
    String defaultTimingProfile = "<wsp:Policy xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\" xmlns:wsrm=\"http://schemas.xmlsoap.org/ws/2004/03/rm\"> <wsrm:BaseRetransmissionInterval Milliseconds=\"3000\" wsp:Usage=\"wsp:Observed\" /> <wsrm:ExponentialBackoff wsp:Usage=\"wsp:Observed\" /> <wsrm:InactivityTimeout Milliseconds=\"86400000\" wsp:Usage=\"wsp:Observed\" />  <wsrm:AcknowledgementInterval Milliseconds=\"1000\" wsp:Usage=\"wsp:Observed\" />  </wsp:Policy>"; 
    
    return defaultTimingProfile;
  }


  /** Create a sequence policies instance based on its marshalled
      representation. */
  public WsrmSequencePolicies 
  getWsrmSequencePolicies(byte[] marshalledBytes) {

    if (marshalledBytes == null) {
      System.out.println(moduleName + "The marshalled bytes are null");
    }

    WsrmSequencePolicies wsrmSequencePolicies = 
      new WsrmSequencePoliciesImpl(marshalledBytes);

    return wsrmSequencePolicies;
  }


   /** Get the sequenceRef document. The way this method works is as follows.
      SequenceRef elements should be contained within the wsp:AppliesTo 
      element which in turn must be encapsulated within a PolicyAttachment
      element. If any of the predecessors are missing, this method will 
      return a NULL. */
  public SequenceRefDocument 
  getSequenceRefDocument(EnvelopeDocument envelopeDocument) {
    SequenceRefDocument sequenceRefDocument = null;
    XmlCursor envelopeCursor = envelopeDocument.newCursor();
    
    boolean foundPolicyAttachment = 
      qNameLocator.locateQName(envelopeCursor, 
			       wspQNames.getPolicyAttachment());
    if (!foundPolicyAttachment) {
      System.out.println(moduleName + "Could not find PolicyAttachment");
      return sequenceRefDocument;
    }
  
    try {
      PolicyAttachmentDocument policyAttachmentDocument =
	PolicyAttachmentDocument.Factory.parse( envelopeCursor.xmlText() );
      
      envelopeCursor.dispose();
      
      AppliesToDocument.AppliesTo appliesTo = 
	policyAttachmentDocument.getPolicyAttachment().getAppliesTo();
      
      
      XmlCursor appliesToCursor = appliesTo.newCursor();
      
      boolean foundSequenceRef = 
	qNameLocator.locateQName(appliesToCursor, 
				 wsrmQNames.getSequenceRef());
      
      if (foundSequenceRef) {
	sequenceRefDocument = 
	  SequenceRefDocument.Factory.parse(appliesToCursor.xmlText());
      }
      appliesToCursor.dispose();

    } catch (XmlException xmlEx) {
      /** If there are problems, we simply assume that it is not specified
	  and proceed to return NULL. The logic of the method invocation is
	  supposed to cope with NULL values. */
      System.out.println(moduleName + "Problems parsing the document for " +
			 "policyAttachments, AppliesTo and SequenceRef" +
			 " elements " + xmlEx.toString() );
      
    }
    return sequenceRefDocument;
  }
  

}
